/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.portlets.googleanalytics;

import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.portlet.PortletPreferences;
import javax.portlet.RenderRequest;
import javax.servlet.http.HttpServletRequest;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.groups.IEntityGroup;
import org.apereo.portal.groups.IGroupMember;
import org.apereo.portal.portlets.PortletPreferencesJsonDao;
import org.apereo.portal.security.IPerson;
import org.apereo.portal.security.IPersonManager;
import org.apereo.portal.services.GroupService;
import org.apereo.portal.url.IPortalRequestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.portlet.bind.annotation.RenderMapping;

@Controller
@RequestMapping("VIEW")
public class GoogleAnalyticsController {
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private PortletPreferencesJsonDao portletPreferencesJsonDao;

    @Autowired private IPortalRequestUtils portalRequestUtils;

    @Autowired private IPersonManager personManager;

    @Autowired
    public void setPortletPreferencesJsonDao(PortletPreferencesJsonDao portletPreferencesJsonDao) {
        this.portletPreferencesJsonDao = portletPreferencesJsonDao;
    }

    @RenderMapping
    public String renderAnalyticsHeader(RenderRequest request, ModelMap model) throws IOException {

        // For which user account are we logging portal activity?
        final HttpServletRequest req = portalRequestUtils.getCurrentPortalRequest();
        final IPerson person = personManager.getPerson(req);
        final String username = person.getUserName();

        final IGroupMember groupMember = GroupService.getGroupMember(username, IPerson.class);
        final Map<String, Boolean> isMemberCache = new HashMap<>();

        final PortletPreferences preferences = request.getPreferences();
        final JsonNode config =
                this.portletPreferencesJsonDao.getJsonNode(
                        preferences, GoogleAnalyticsConfigController.CONFIG_PREF_NAME);

        final JsonNode propertyConfig = config.get("defaultConfig");
        this.filterAnalyticsGroups(groupMember, propertyConfig, isMemberCache);

        final JsonNode hosts = config.get("hosts");
        if (hosts != null) {
            for (final Iterator<JsonNode> hostsItr = hosts.elements(); hostsItr.hasNext(); ) {
                final JsonNode institution = hostsItr.next();
                this.filterAnalyticsGroups(groupMember, institution, isMemberCache);
            }
        }

        model.put("data", config);

        if (propertyConfig == null || propertyConfig.get("propertyId") == null) {
            return "jsp/GoogleAnalytics/noop";
        } else {
            return "jsp/GoogleAnalytics/init";
        }
    }

    /** Remove groups from the AnalyticsConfig that the current user is not a member of */
    private void filterAnalyticsGroups(
            IGroupMember groupMember, JsonNode config, Map<String, Boolean> isMemberCache) {
        if (config == null) {
            return;
        }

        final JsonNode dimensionGroups = config.get("dimensionGroups");
        if (dimensionGroups == null) {
            return;
        }

        for (final Iterator<JsonNode> groupItr = dimensionGroups.elements(); groupItr.hasNext(); ) {
            final JsonNode group = groupItr.next();

            final JsonNode valueNode = group.get("value");
            if (valueNode == null) {
                continue;
            }

            final String groupName = valueNode.asText();

            Boolean isMember = isMemberCache.get(groupName);
            if (isMember == null) {
                isMember = isMember(groupMember, groupName);
                isMemberCache.put(groupName, isMember);
            }

            if (!isMember) {
                groupItr.remove();
            }
        }
    }

    /**
     * Check if the user is a member of the specified group name
     *
     * <p>Internal search, thus case sensitive.
     */
    private boolean isMember(IGroupMember groupMember, String groupName) {
        try {
            IEntityGroup group = GroupService.findGroup(groupName);
            if (group != null) {
                return groupMember.isDeepMemberOf(group);
            }

            final EntityIdentifier[] results =
                    GroupService.searchForGroups(
                            groupName, GroupService.SearchMethod.DISCRETE, IPerson.class);
            if (results == null || results.length == 0) {
                this.logger.warn(
                        "No portal group found for '{}' no users will be placed in that group for analytics",
                        groupName);
                return false;
            }

            if (results.length > 1) {
                this.logger.warn(
                        "{} groups were found for groupName '{}'. The first result will be used.",
                        results.length,
                        groupName);
            }

            group = (IEntityGroup) GroupService.getGroupMember(results[0]);
            return groupMember.isDeepMemberOf(group);
        } catch (Exception e) {
            this.logger.warn(
                    "Failed to determine if {} is a member of {}, returning false",
                    groupMember,
                    groupName,
                    e);
            return false;
        }
    }
}
